
#include <ql/alloc_context.h>

#include <ql/objtypes.h>
#include <ql/alloc.h>
#include <ql/stream.h>

#include <stdlib.h>
#include <string.h>
#include <assert.h>

/*
 * constants
 */
static const char * g_nil_obj_string = "nil";

/*
 * vtbl functions
 */

/*
 * generic vtbl functions
 */

static struct QlObj * atom_get_ref_object(struct QlObj * obj, size_t index)
{
    assert((obj != NULL) && (index == 0));
    return NULL;
}

static void fake_dispose(struct QlObj * obj)
{
    assert(obj != NULL);
}

/*
 * t object
 */

static void t_write_as_string(struct QlObj * obj, struct QlStream * stream)
{
    assert((obj != NULL)  && (stream != NULL));
    ql_stream_putc(stream, 't');
}

static const struct QlObjVtbl g_t_obj_vtbl =
{
    QL_OBJ_T,
    "t",
    &atom_get_ref_object,
    &fake_dispose,
    &t_write_as_string
};

static struct QlObj g_t_obj =
{
    &g_t_obj_vtbl
};

/*
 * nil object
 */

static void nil_write_as_string(struct QlObj * obj, struct QlStream * stream)
{
    assert((obj != NULL)  && (stream != NULL));
    ql_stream_puts(stream, g_nil_obj_string);
}

static const struct QlObjVtbl g_nil_obj_vtbl =
{
    QL_OBJ_NIL,
    "nil",
    &atom_get_ref_object,
    &fake_dispose,
    &nil_write_as_string
};

static struct QlObj g_nil_obj =
{
    &g_nil_obj_vtbl
};

/*
 * getters
 */

struct QlObj * ql_get_t()
{
    return &g_t_obj;
}

struct QlObj * ql_get_nil()
{
    return &g_nil_obj;
}

/*
 * cdr vtbl functions
 */


static struct QlObj * cdr_get_ref_object(struct QlObj * obj, size_t index)
{
    struct QlCdr * cdr = (struct QlCdr *)obj;

    if (index == 0)
    {
        return (cdr->car != NULL ? cdr->car : (struct QlObj *)cdr->cdr);
    }
    else if ((index == 1) && (NULL != cdr->car))
    {
        return (struct QlObj *)cdr->cdr;
    }

    return NULL;
}

static void cdr_dispose(struct QlObj * obj);

static void cdr_write_as_string(struct QlObj * obj, struct QlStream * stream)
{
    struct QlCdr * cdr = (struct QlCdr *)obj;
    assert((obj != NULL)  && (stream != NULL));
    
    if (cdr->car == NULL)
    {
        assert(cdr->cdr == NULL);
        ql_stream_puts(stream, g_nil_obj_string);
    }
    else
    {
        ql_stream_putc(stream, '(');

        ql_write_as_string(cdr->car, stream);
        cdr = cdr->cdr;

        for (;cdr != NULL; cdr = cdr->cdr)
        {
            ql_stream_putc(stream, ' ');
            ql_write_as_string(cdr->car, stream);
        }
        
        ql_stream_putc(stream, ')');
    }
}

static void id_dispose(struct QlObj * obj);
static void id_write_as_string(struct QlObj * obj, struct QlStream * stream);

static void int_dispose(struct QlObj * obj);
static void int_write_as_string(struct QlObj * obj, struct QlStream * stream)
{
    struct QlInt * int_obj = (struct QlInt *)obj;
    char buf[32]; /* TODO: make an other approach to stringifying integer number */
    assert((obj != NULL) && (ql_type(obj) == QL_OBJ_INT) && (stream != NULL));

    itoa(int_obj->value, buf, 10);
    ql_stream_puts(stream, buf);
}

static void dbl_dispose(struct QlObj * obj);
static void dbl_write_as_string(struct QlObj * obj, struct QlStream * stream)
{
    struct QlDouble * dbl_obj = (struct QlDouble *)obj;
    char buf[64]; /* TODO: make an other approach to stringifying double number */
    assert((obj != NULL) && (ql_type(obj) == QL_OBJ_DOUBLE) && (stream != NULL));

    gcvt(dbl_obj->value, 10, buf);
    ql_stream_puts(stream, buf);
}

static void str_dispose(struct QlObj * obj);
static void str_write_as_string(struct QlObj * obj, struct QlStream * stream)
{
    struct QlString * s = (struct QlString *)obj;
    assert((obj != NULL) && (ql_type(obj) == QL_OBJ_STRING) && (stream != NULL));
    ql_stream_puts(stream, s->str);
}


/*
 * end of vtbl functions
 */

/*
 * objects allocation typedefs
 */

/*
 * list (aka cdr) allocation
 */

#define QL_FIXED_ALLOC_NS(n)                cdr_##n
#define QL_FIXED_ALLOC_ELEMENT_TYPE         struct QlCdr
#define QL_FIXED_ALLOC_INITIAL_CHUNK_SIZE   (32)
#define QL_FIXED_ALLOC_XMALLOC              ql_malloc
#define QL_FIXED_ALLOC_XFREE                ql_free
#define QL_FIXED_ALLOC_FREE_FUNCTION_REQUIRED
#define QL_FIXED_ALLOC_FOREACH_REQUIRED
#include <qltemplates/fixed_alloc.h>


/*
 * id allocator
 */
struct Token
{
    struct Token *  next;
    struct QlId     id;
    size_t          len;
    char            str[1];
};

/*
 * hash table size MUST BE power of 2
 */
#define QL_IDS_HASH_TABLE_SIZE          (65536)

#define QL_IDS_TOKENS_ARRAY_GROW_SIZE   (4096)

struct IdsStorage
{
    struct Token *  hash_table[QL_IDS_HASH_TABLE_SIZE];

    struct Token ** tokens;
    size_t          size;
    size_t          allocated;
};

static void free_ids_storage(struct IdsStorage * s);

/*
 * integer number allocator
 */
#define QL_FIXED_ALLOC_NS(n)                int_##n
#define QL_FIXED_ALLOC_ELEMENT_TYPE         struct QlInt
#define QL_FIXED_ALLOC_INITIAL_CHUNK_SIZE   (256)
#define QL_FIXED_ALLOC_XMALLOC              ql_malloc
#define QL_FIXED_ALLOC_XFREE                ql_free
#define QL_FIXED_ALLOC_FREE_FUNCTION_REQUIRED
#define QL_FIXED_ALLOC_FOREACH_REQUIRED
#include <qltemplates/fixed_alloc.h>

/*
 * double number allocator
 */
#define QL_FIXED_ALLOC_NS(n)                dbl_##n
#define QL_FIXED_ALLOC_ELEMENT_TYPE         struct QlDouble
#define QL_FIXED_ALLOC_INITIAL_CHUNK_SIZE   (256)
#define QL_FIXED_ALLOC_XMALLOC              ql_malloc
#define QL_FIXED_ALLOC_XFREE                ql_free
#define QL_FIXED_ALLOC_FREE_FUNCTION_REQUIRED
#define QL_FIXED_ALLOC_FOREACH_REQUIRED
#include <qltemplates/fixed_alloc.h>

/*
 * strings allocation
 */

struct InternalStr
{
    struct InternalStr *    next;

    struct QlString         ql_str;
};

struct StringAllocator
{
    struct InternalStr *    str_root;
};

static void free_string_allocator(struct StringAllocator * s);

/*
 * end of objects allocation typedefs
 */

/*
 * garbage collector context
 */

#define QL_TREE_NS(name)            nd_##name
#define QL_TREE_KEY_TYPE            struct QlObj *
#define QL_TREE_REMOVE_NODE_REQUIRED
#define QL_TREE_COMPARE(l, r)       (int)(l - r)
#define QL_TREE_XMALLOC             ql_malloc
#define QL_TREE_XFREE               ql_free
#define QL_TREE_FOREACH_REQUIRED

#include <qltemplates/avl_tree.h>

struct GcContext
{
    /*
     * non-disposable objects tree
     */
    nd_Tree     nd_tree;

#ifdef QL_GC_DEBUG
    void *      dispose_callback_context;

    void (* dispose_callback)(void * context, struct QlObj * disposed_obj);
#endif // QL_GC_DEBUG
};

static void init_gc_context(struct GcContext * gc_context);

static void free_gc_context(struct GcContext * gc_context);

/*
 * garbage collector context END
 */

/*
 * definition of the allocator context
 */
struct QlAllocContext
{
    /*
     * list (aka cdr) allocator
     */
    cdr_Allocator           cdr_allocator;

    /*
     * ids storage
     */
    struct IdsStorage       ids_storage;

    /*
     * integer number allocator
     */
    int_Allocator           int_allocator;

    /*
     * double number allocator
     */
    dbl_Allocator           dbl_allocator;

    /*
     * strings allocator
     */
    struct StringAllocator  str_allocator;

    /*
     * garbage collector
     */
    struct GcContext        gc_context;

    /*
     * user-side callback called within alloc_emit_error
     */
    QlAllocFailureCallback  failure_callback_p;

    /*
     * virtual method tables - needed to be here in order to be able to get offset to the current alloc context
     */
    struct QlObjVtbl        cdr_vtbl;
    struct QlObjVtbl        id_vtbl;
    struct QlObjVtbl        int_vtbl;
    struct QlObjVtbl        dbl_vtbl;
    struct QlObjVtbl        str_vtbl;
};

static void init_vtbls(struct QlAllocContext * c)
{
    /* cdr vtbl */
    c->cdr_vtbl.type_id = QL_OBJ_CDR;
    c->cdr_vtbl.type_name = "cdr";
    c->cdr_vtbl.get_ref_object = &cdr_get_ref_object;
    c->cdr_vtbl.dispose = &cdr_dispose;
    c->cdr_vtbl.write_as_string = &cdr_write_as_string;

    /* id vtbl */
    c->id_vtbl.type_id = QL_OBJ_ID;
    c->id_vtbl.type_name = "symbol";
    c->id_vtbl.dispose = &id_dispose;
    c->id_vtbl.get_ref_object = &atom_get_ref_object;
    c->id_vtbl.write_as_string = &id_write_as_string;

    /* int vtbl */
    c->int_vtbl.type_id = QL_OBJ_INT;
    c->int_vtbl.type_name = "int";
    c->int_vtbl.dispose = &int_dispose;
    c->int_vtbl.get_ref_object = &atom_get_ref_object;
    c->int_vtbl.write_as_string = &int_write_as_string;

    /* dbl vtbl */
    c->dbl_vtbl.type_id = QL_OBJ_DOUBLE;
    c->dbl_vtbl.type_name = "double";
    c->dbl_vtbl.dispose = &dbl_dispose;
    c->dbl_vtbl.get_ref_object = &atom_get_ref_object;
    c->dbl_vtbl.write_as_string = &dbl_write_as_string;

    /* str vtbl */
    c->str_vtbl.type_id = QL_OBJ_STRING;
    c->str_vtbl.type_name = "string";
    c->str_vtbl.dispose = &str_dispose;
    c->str_vtbl.get_ref_object = &atom_get_ref_object;
    c->str_vtbl.write_as_string = &str_write_as_string;
}

static struct QlAllocContext * get_cdr_ac(const struct QlObj * obj)
{
    assert(ql_type(obj) == QL_OBJ_CDR);
    return (struct QlAllocContext *)(((char *)obj->vtbl) - offsetof(struct QlAllocContext, cdr_vtbl));
}

static struct QlAllocContext * get_id_ac(const struct QlObj * obj)
{
    assert(ql_type(obj) == QL_OBJ_ID);
    return (struct QlAllocContext *)(((char *)obj->vtbl) - offsetof(struct QlAllocContext, id_vtbl));
}

static struct QlAllocContext * get_int_ac(const struct QlObj * obj)
{
    assert(ql_type(obj) == QL_OBJ_INT);
    return (struct QlAllocContext *)(((char *)obj->vtbl) - offsetof(struct QlAllocContext, int_vtbl));
}

static struct QlAllocContext * get_dbl_ac(const struct QlObj * obj)
{
    assert(ql_type(obj) == QL_OBJ_DOUBLE);
    return (struct QlAllocContext *)(((char *)obj->vtbl) - offsetof(struct QlAllocContext, dbl_vtbl));
}

static struct QlAllocContext * get_str_ac(const struct QlObj * obj)
{
    assert(ql_type(obj) == QL_OBJ_STRING);
    return (struct QlAllocContext *)(((char *)obj->vtbl) - offsetof(struct QlAllocContext, str_vtbl));
}

/*
 * implementation for core functions of AllocContext
 */

void ql_alloc_emit_error(struct QlAllocContext * c, QlErr error)
{
    if (NULL != c->failure_callback_p)
    {
        c->failure_callback_p(c, error);
    }

    assert(!"ql allocator failure happened");
    exit(-1);
}

void ql_set_alloc_failure_callback(struct QlAllocContext * c, QlAllocFailureCallback failure_callback_p)
{
    c->failure_callback_p = failure_callback_p;
}

struct QlAllocContext * ql_create_alloc_context()
{
    struct QlAllocContext * c = ql_mallocz(sizeof(struct QlAllocContext));

    cdr_init_allocator(&c->cdr_allocator);
    int_init_allocator(&c->int_allocator);
    dbl_init_allocator(&c->dbl_allocator);

    init_gc_context(&c->gc_context);

    init_vtbls(c);

    return c;
}

void ql_free_alloc_context(struct QlAllocContext * c)
{
    cdr_uninit_allocator(&c->cdr_allocator);
    int_uninit_allocator(&c->int_allocator);
    dbl_uninit_allocator(&c->dbl_allocator);

    free_ids_storage(&c->ids_storage);
    free_string_allocator(&c->str_allocator);
    free_gc_context(&c->gc_context);

    ql_free(c);
}

/*
 * object allocation impl
 */


/*
 * list allocation
 */

static void cdr_dispose(struct QlObj * obj)
{
    struct QlAllocContext * c;
    struct QlCdr * cdr = (struct QlCdr *)obj;
    assert(cdr != NULL);

    c = get_cdr_ac(obj);
    cdr_free_elem(&c->cdr_allocator, cdr);
}

struct QlCdr * ql_alloc_cdr(struct QlAllocContext * c)
{
    struct QlCdr * result = cdr_alloc_elem(&c->cdr_allocator);
    result->car = NULL;
    result->cdr = NULL;

    result->vtbl = &c->cdr_vtbl;

    return result;
}

/*
 * allocates identifier or returns existing one
 */
typedef unsigned long QlHashInt;

static QlHashInt
hash_from_str(const char * src_str, size_t len)
{
    const unsigned char * str = (const unsigned char *)src_str;
    size_t n = len;
    QlHashInt h = 0;
    QlHashInt g;

    while (n--)
    {
        h = (h << 4) + *str++;
        if ((g = (h & 0xf0000000)) != 0)
        {
            h ^= g >> 23;
        }

        h &= ~g;
    }

    return h;
}

static struct Token *
get_token(struct QlAllocContext * c, const char * str, size_t len)
{
    struct IdsStorage * s = &c->ids_storage;
    QlHashInt hashed_index = hash_from_str(str, len) & (QL_IDS_HASH_TABLE_SIZE - 1);
    struct Token * token = s->hash_table[hashed_index];

    // find token
    while (NULL != token)
    {
        if ((token->len == len) && (0 == memcmp(str, token->str, len)))
        {
            return token;
        }

        token = token->next;
    }

    // allocate new one, cause there is no such a token
    token = ql_malloc(sizeof(struct Token) + len);
    token->len = len;
    memcpy(token->str, str, len);

    // add terminating zero
    token->str[len] = 0;

    // add this one to the hash table
    token->next = s->hash_table[hashed_index];
    s->hash_table[hashed_index] = token;

    // grow token table if needed
    if ((s->size + 1) > s->allocated)
    {
        s->allocated += QL_IDS_TOKENS_ARRAY_GROW_SIZE;
        s->tokens = ql_realloc(s->tokens, s->allocated * sizeof(struct Token));
    }

    // perform id initialization
    token->id.vtbl = &c->id_vtbl;
    token->id.token = s->size;
    token->id.assoc_obj = NULL;

    // add to the array
    s->tokens[s->size++] = token;

    return token;
}

struct QlId * ql_alloc_id(struct QlAllocContext *  c, const char * id_str)
{
    return ql_alloc_id_len(c, id_str, strlen(id_str));
}

struct QlId * ql_alloc_id_len(struct QlAllocContext *  c, const char * id_str, size_t id_len)
{
    struct Token *      token = get_token(c, id_str, id_len);
    struct QlId *       result;

    result = &token->id;

    return result;
}

const char * ql_get_id_str(const struct QlId * id)
{
    struct QlAllocContext * c = get_id_ac((const struct QlObj *)id);
    const struct IdsStorage * s = &c->ids_storage;
    return s->tokens[id->token]->str;
}

static void id_dispose(struct QlObj * obj)
{
    assert(obj != NULL);
}

static void id_write_as_string(struct QlObj * obj, struct QlStream * stream)
{
    struct QlId * id = (struct QlId *)obj;
    const struct IdsStorage * s;
    const struct Token * t;

    assert((id != NULL)  && (stream != NULL));

    s = &(get_id_ac(obj)->ids_storage);
    t = s->tokens[id->token];

    ql_stream_write(stream, t->str, t->len);
}

static void free_ids_storage(struct IdsStorage * s)
{
    size_t i;

    // free each token
    for (i = 0; i < s->size; ++i)
    {
        ql_free(s->tokens[i]);
    }

    // free tokens table itself
    ql_free(s->tokens);
}

/*
 * atomic objects allocator: for integer, double and string types
 */

static void int_dispose(struct QlObj * obj)
{
    struct QlAllocContext * c = get_int_ac(obj);
    int_free_elem(&c->int_allocator, (struct QlInt *)obj);
}

struct QlInt * ql_alloc_integer(struct QlAllocContext * c, int val)
{
    struct QlInt * result = int_alloc_elem(&c->int_allocator);

    result->vtbl = &c->int_vtbl;
    result->value = val;

    return result;
}

static void dbl_dispose(struct QlObj * obj)
{
    struct QlAllocContext * c = get_dbl_ac(obj);
    dbl_free_elem(&c->dbl_allocator, (struct QlDouble *)obj);
}

struct QlDouble * ql_alloc_double(struct QlAllocContext * c, double val)
{
    struct QlDouble * result = dbl_alloc_elem(&c->dbl_allocator);

    result->vtbl = &c->dbl_vtbl;
    result->value = val;

    return result;
}

/*
 * string object allocator
 */

static void str_dispose(struct QlObj * obj)
{
    assert(obj != NULL);
}

static void free_string_allocator(struct StringAllocator * s)
{
    struct InternalStr * is = s->str_root;

    while (is != NULL)
    {
        struct InternalStr * next = is->next;
        ql_free(is);
        is = next;
    }
}

struct QlString * ql_alloc_string(struct QlAllocContext * c, const char * str)
{
    return ql_alloc_string_len(c, str, strlen(str));
}

struct QlString * ql_alloc_string_len(struct QlAllocContext * c, const char * str, size_t str_len)
{
    struct InternalStr * s = ql_malloc(sizeof(struct InternalStr) + str_len);

    s->ql_str.vtbl = &c->str_vtbl;
    memcpy(s->ql_str.str, str, str_len);
    s->ql_str.str[str_len] = 0;

    return &s->ql_str;
}

/*
 * object allocation impl END
 */

/*
 * garbage collector
 */
bool ql_gc_keep_alive(struct QlAllocContext * c, struct QlObj * obj, bool keep_alive)
{
    struct GcContext *  gc_context = &c->gc_context;
    nd_Tree *           tree = &gc_context->nd_tree;
    bool                result;
    
    assert(obj != NULL);

    if (keep_alive)
    {
        nd_add_node_ext(tree, obj, &result);
        result = !result;
    }
    else
    {
        result = nd_remove_node(tree, obj);
    }

    return result;
}

static void gc_reset_mark(struct QlObj * obj)
{
    size_t i = 0;

    obj->gc_mark = 0;

    /* recursively set gc mark for all the internal objects */
    for (;;++i)
    {
        struct QlObj * next = ql_get_ref_object(obj, i);
        if (next == NULL)
        {
            break;
        }

        gc_reset_mark(next);
    }
}

static void gc_inc_mark(struct QlObj * obj)
{
    size_t i = 0;

    ++obj->gc_mark;

    /* recursively increase gc mark for all the internal objects */
    for (;;++i)
    {
        struct QlObj * next = ql_get_ref_object(obj, i);
        if (next == NULL)
        {
            break;
        }

        gc_inc_mark(next);
    }
}

#ifdef QL_GC_DEBUG
static struct QlAllocContext * context_from_obj(struct QlObj * obj)
{
    struct QlAllocContext * c = NULL;

    switch (ql_type(obj))
    {
    case QL_OBJ_NIL:
    case QL_OBJ_T:
        break; /* either nil and t are not expected to be disposed */

    case QL_OBJ_ID:
        c = get_id_ac(obj);
        break;

    case QL_OBJ_INT:
        c = get_int_ac(obj);
        break;

    case QL_OBJ_DOUBLE:
        c = get_dbl_ac(obj);
        break;

    case QL_OBJ_STRING:
        c = get_str_ac(obj);
        break;

    case QL_OBJ_CDR:
        c = get_cdr_ac(obj);
        break;

    default:
        assert(!"Not implemented");
    }

    return c;
}
#endif

/*
 * all the objects collected by gc shall be disposed by this functions
 */
static void gc_dispose(struct QlObj * obj)
{
#ifdef QL_GC_DEBUG
    struct QlAllocContext * c = context_from_obj(obj);
    if (c)
    {
        struct GcContext * gc_context = &c->gc_context;
        if (gc_context->dispose_callback != NULL)
        {
            gc_context->dispose_callback(gc_context->dispose_callback_context, obj);
        }
    }
#endif

    ql_dispose(obj);
}

static void gc_dec_mark(struct QlObj * obj)
{
    size_t i;

    if (obj->gc_mark == 0)
    {
        gc_dispose(obj);
        return;
    }

    --obj->gc_mark;
    assert(obj->gc_mark >= 0);

    /* recursively decrease gc mark for all the internal objects */
    for (i = 0;;++i)
    {
        struct QlObj * next = ql_get_ref_object(obj, i);
        if (next == NULL)
        {
            break;
        }

        gc_dec_mark(next);
    }

    if (0 == obj->gc_mark)
    {
        gc_dispose(obj);
    }
}

static void nd_tree_gc_foreach_callback(void * ctx, nd_Node * node)
{
    assert((ctx == NULL) && (node != NULL));
    gc_inc_mark(node->key);
}

/* typedefs needed to suppress warning */
typedef void (* CdrForeachCallback)(void * c, struct QlCdr * o);
typedef void (* IntForeachCallback)(void * c, struct QlInt * o);
typedef void (* DblForeachCallback)(void * c, struct QlDouble * o);

static void disposable_objects_foreach(struct QlAllocContext * c, void * context, void (* foreach_callback)(void * c, struct QlObj * o))
{
    cdr_allocator_foreach(&c->cdr_allocator, context, (CdrForeachCallback)foreach_callback);
    int_allocator_foreach(&c->int_allocator, context, (IntForeachCallback)foreach_callback);
    dbl_allocator_foreach(&c->dbl_allocator, context, (DblForeachCallback)foreach_callback);
}

static void gc_foreach_disposable_obj_reset_mark_callback(void * ctx, struct QlObj * obj)
{
    assert((ctx == NULL) && (obj != NULL));
    gc_reset_mark(obj);
}

static void gc_foreach_disposable_obj_dec_mark_callback(void * ctx, struct QlObj * obj)
{
    assert((ctx == NULL) && (obj != NULL));
    gc_dec_mark(obj);
}


static void gc_id_foreach(struct QlAllocContext * c)
{
    struct IdsStorage * s = &c->ids_storage;
    size_t i;

    for (i = 0; i < s->size; ++i)
    {
        struct QlId * id = &s->tokens[i]->id;
        struct QlObj * assoc_obj = id->assoc_obj;

        if (assoc_obj != NULL)
        {
            /*
             * increase mark at the associated object
             */
            gc_inc_mark(assoc_obj);
        }
    }
}

void ql_gc_collect(struct QlAllocContext * c)
{
    struct GcContext * gc_context = &c->gc_context;
    nd_Tree * tree = &gc_context->nd_tree;

    /* clear mark from all the disposable objects */
    disposable_objects_foreach(c, NULL, &gc_foreach_disposable_obj_reset_mark_callback);

    /* mark objects that tied to certain id */
    nd_tree_foreach(tree, NULL, &nd_tree_gc_foreach_callback);

    /* decrease mark at the given objects */
    disposable_objects_foreach(c, NULL, &gc_foreach_disposable_obj_dec_mark_callback);
}

static void init_gc_context(struct GcContext * gc_context)
{
    nd_init_tree(&gc_context->nd_tree);
}

static void free_gc_context(struct GcContext * gc_context)
{
    nd_uninit_tree(&gc_context->nd_tree);
}

#ifdef QL_GC_DEBUG
void ql_gc_register_dispose_callback(struct QlAllocContext * c,
                                     void * context,
                                     void (* dispose_callback)(void * context, struct QlObj * disposed_obj))
{
    struct GcContext * gc = &c->gc_context;

    gc->dispose_callback_context = context;
    gc->dispose_callback = dispose_callback;
}
#endif // QL_GC_DEBUG

/*
 * garbage collector implementation ENDs
 */
